Local Interpretable Machine Learning
We will use the King County housing data.
data("house_prices")
# Create log_price and drop price variable
house_prices <- house_prices %>%
mutate(log_price = log(price, base = 10)) %>%
# make all integers numeric ... fixes prediction problem
mutate(across(where(is.integer), as.numeric)) %>%
select(-price)
Part 1
Choose 3 new observations and do the following for each observation: * Construct a break-down plot using the default ordering. Interpret the resulting graph. Which variables contribute most to each observation’s prediction? * Construct a SHAP graph and interpret it. Does it tell a similar story to the break-down plot? * Construct a LIME graph (follow my code carefully). How close is each original prediction to the prediction from the local model? Interpret the result. You can also try using fewer or more variables in the local model than Lisa used in the example.
set.seed(494)
house_split <- initial_split(house_prices,
prop = .75)
house_train <- training(house_split)
house_test <- testing(house_split)
house_ranger_recipe <-
recipe(formula = log_price ~ .,
data = house_train) %>%
step_date(date,
features = "month") %>%
update_role(all_of(c("id",
"date")),
new_role = "evaluative")
house_ranger_spec <-
rand_forest(mtry = 6,
min_n = 10,
trees = 200) %>%
set_mode("regression") %>%
set_engine("ranger")
house_ranger_workflow <-
workflow() %>%
add_recipe(house_ranger_recipe) %>%
add_model(house_ranger_spec)
set.seed(494)
house_ranger_fit <- house_ranger_workflow %>%
fit(house_train)
house_rf_explain <-
explain_tidymodels(
model = house_ranger_fit,
data = house_train %>% select(-log_price),
y = house_train %>% pull(log_price),
label = "Random Forest"
)
## Preparation of a new explainer is initiated
## -> model label : Random Forest
## -> data : 16210 rows 20 cols
## -> data : tibble converted into a data.frame
## -> target variable : 16210 values
## -> predict function : yhat.workflow will be used ( [33m default [39m )
## -> predicted values : No value for predict function target column. ( [33m default [39m )
## -> model_info : package tidymodels , ver. 0.1.3 , task regression ( [33m default [39m )
## -> predicted values : numerical, min = 5.020731 , mean = 5.665231 , max = 6.696937
## -> residual function : difference between y and yhat ( [33m default [39m )
## -> residuals : numerical, min = -0.3328418 , mean = 0.0004361088 , max = 0.2467335
## [32m A new explainer has been created! [39m
set.seed(494)
new_obs1 <- house_test %>% slice(4239)
new_obs2 <- house_test %>% slice(294)
new_obs3 <- house_test %>% slice(3)
10^(new_obs1$log_price)
## [1] 470000
10^(new_obs2$log_price)
## [1] 425000
10^(new_obs3$log_price)
## [1] 4e+05
Break-Down Plots
set.seed(494)
house_pp_rf1 <- predict_parts(explainer = house_rf_explain,
new_observation = new_obs1,
type = "break_down")
house_pp_rf2 <- predict_parts(explainer = house_rf_explain,
new_observation = new_obs2,
type = "break_down")
house_pp_rf3 <- predict_parts(explainer = house_rf_explain,
new_observation = new_obs3,
type = "break_down")
plot(house_pp_rf1)

plot(house_pp_rf2)

plot(house_pp_rf3)

The average log price remains the same across all observations, as it is the average log price when applied to all of the training data. This log price is 5.665, or $462,381. We see that holding latitude constant at 47.698 affects the overall average price the most, and increases the log price by 0.084, or $98,666.95. The predicted log price for this observation is 5.653, or $449,779.90.
The second plot shows us that fixing our latitude at 47.5197 decreases the average log price by 0.051, or $51,231.30. We also see that holding this latitude constant affects the price the most. Finally, the predicted log price for this observation is 5.599, or $397,191.50.
The third plot shows us that fixing our latitude at 47.6127 increases the log price by 0.109, or $131,911.10. Holding this latitude constant affects the predicted price the most. This predicted log price is 5.59, or $389,045.10.
Shapley Additive Explanationss (SHAP) Plots
house_rf_shap1 <-predict_parts(explainer = house_rf_explain,
new_observation = new_obs1,
type = "shap",
B = 10
)
house_rf_shap2 <-predict_parts(explainer = house_rf_explain,
new_observation = new_obs2,
type = "shap",
B = 10
)
house_rf_shap3 <-predict_parts(explainer = house_rf_explain,
new_observation = new_obs3,
type = "shap",
B = 10
)
plot(house_rf_shap1)

plot(house_rf_shap2)

plot(house_rf_shap3)

- The first SHAP plot shows a similar effect to the first break-down plot, where grade is the highest contributing variable, however this time it is in a negative way. The second and third SHAP plots both show that latitude is the highest contributing variable, which is in accordance with the break-down plots, this time both affecting the predicted price in the same way as the break-down plots.
Local Interpretable Model-Agnostic Explanation (LIME) Plots
set.seed(494)
model_type.dalex_explainer <- DALEXtra::model_type.dalex_explainer
predict_model.dalex_explainer <- DALEXtra::predict_model.dalex_explainer
house_lime_rf1 <- predict_surrogate(explainer = house_rf_explain,
new_observation = new_obs1 %>%
select(-log_price),
n_features = 5,
n_permutations = 1000,
type = "lime")
house_lime_rf2 <- predict_surrogate(explainer = house_rf_explain,
new_observation = new_obs2 %>%
select(-log_price),
n_features = 5,
n_permutations = 1000,
type = "lime")
house_lime_rf3 <- predict_surrogate(explainer = house_rf_explain,
new_observation = new_obs3 %>%
select(-log_price),
n_features = 5,
n_permutations = 1000,
type = "lime")
house_lime_rf1 %>%
select(model_r2, model_prediction, prediction) %>%
distinct()
house_lime_rf2 %>%
select(model_r2, model_prediction, prediction) %>%
distinct()
house_lime_rf3 %>%
select(model_r2, model_prediction, prediction) %>%
distinct()
set.seed(494)
plot(house_lime_rf1) +
labs(x = "Variable")

plot(house_lime_rf2) +
labs(x = "Variable")

plot(house_lime_rf3) +
labs(x = "Variable")

Plot 1 shows us that the predicted log price is about 5.577, and that grade is once again the most important in the local model. This model also performs quite poorly, as the explanation fit is only 0.11. This is the exact prediction (to three digits) that the original model had.
Plot 2 shows us that the predicted log price is about 5.86, and that the living room area and latitude are the most important in the local model, albeit in opposite ways. This model performs better than the first, as the explanation fit is 0.45. This is the exact prediction (to two digits) as the original model.
Plot 3 shows us that the predicted log price is about 5.46, and that the grade is the most important to the local model. This model performs the worst out of the three, as the explanation fit is only 0.1. This is the exact prediction (to two digits) as the original model.
Part 2
Describe how you would use the interpretable machine learning tools we’ve learned (both local and global) in future machine learning projects? How does each of them help you?
- These methods provide a helpful alternative to a simple variable importance plot, as we are able to identify variable importance on the level of individual observations. We are able to see why each observation contains the result that it does. The break-down plot method allows us to see how the entire training dataset is affected when we apply a certain model to it. The SHAP plots allow us to see how each variable contributes if we change the order of the variables (in this assignment, the contribution changes because the random forest model is not additive). Through this, we are able to see how true the effect is through the boxplots on top of the SHAP plot. Finally, the LIME plots allow us to see variable importance in conjunction with model performance, which gives us more insight into how trustworthy our result is.
SQL
I will use the airlines data from the SQL database that Lisa used in the example in the tutorial.
Tasks
- Create a SQL chunk and an equivalent R code chunk that does the following: for 2017, for each airport (with its name, not code), and month find the total number of departing flights, the average distance of the flight, and the proportion of flights that arrived more than 20 minutes late. In the R code chunk, write this out to a dataset.
con_air <- dbConnect_scidb("airlines")
DESCRIBE flights
Displaying records 1 - 10
| year |
smallint(4) |
YES |
MUL |
NA |
|
| month |
smallint(2) |
YES |
|
NA |
|
| day |
smallint(2) |
YES |
|
NA |
|
| dep_time |
smallint(4) |
YES |
|
NA |
|
| sched_dep_time |
smallint(4) |
YES |
|
NA |
|
| dep_delay |
smallint(4) |
YES |
|
NA |
|
| arr_time |
smallint(4) |
YES |
|
NA |
|
| sched_arr_time |
smallint(4) |
YES |
|
NA |
|
| arr_delay |
smallint(4) |
YES |
|
NA |
|
| carrier |
varchar(2) |
NO |
MUL |
|
|
DESCRIBE airports
9 records
| faa |
varchar(3) |
NO |
PRI |
|
|
| name |
varchar(255) |
YES |
|
NA |
|
| lat |
decimal(10,7) |
YES |
|
NA |
|
| lon |
decimal(10,7) |
YES |
|
NA |
|
| alt |
int(11) |
YES |
|
NA |
|
| tz |
smallint(4) |
YES |
|
NA |
|
| dst |
char(1) |
YES |
|
NA |
|
| city |
varchar(255) |
YES |
|
NA |
|
| country |
varchar(255) |
YES |
|
NA |
|
late_flights_over20 <- tbl(con_air, "flights") %>%
filter(year == 2017) %>%
group_by(origin, month) %>%
summarize(tot_depart = n(),
avg_dist = mean(distance),
prop_late_over20 = mean(arr_delay > 20)) %>%
inner_join(tbl(con_air, "airports"),
by = c("origin" = "faa")) %>%
select(name, month, tot_depart, avg_dist, prop_late_over20)
late_flights_over20_df <- collect(late_flights_over20)
late_flights_over20_df
late_flights_over20 %>%
show_query()
## <SQL>
## SELECT `origin`, `name`, `month`, `tot_depart`, `avg_dist`, `prop_late_over20`
## FROM (SELECT `origin`, `month`, `tot_depart`, `avg_dist`, `prop_late_over20`, `name`, `lat`, `lon`, `alt`, `tz`, `dst`, `city`, `country`
## FROM (SELECT `origin`, `month`, COUNT(*) AS `tot_depart`, AVG(`distance`) AS `avg_dist`, AVG(`arr_delay` > 20.0) AS `prop_late_over20`
## FROM `flights`
## WHERE (`year` = 2017.0)
## GROUP BY `origin`, `month`) `LHS`
## INNER JOIN `airports` AS `RHS`
## ON (`LHS`.`origin` = `RHS`.`faa`)
## ) `q01`
- With the dataset you wrote out, create a graph that helps illustrate the “worst” airports in terms of late arrivals.
late_plot <- ggplot(late_flights_over20_df %>%
mutate(month = as.factor(month),
mean_prop = mean(prop_late_over20)) %>%
group_by(mean_prop, name) %>%
arrange(desc(mean_prop)) %>%
head(200),
aes(x = tot_depart, y = mean_prop)) +
geom_point(aes(size = avg_dist, color = name), alpha = .5) +
labs(x = "Average Distance Traveled",
y = "Total Departures",
title = "Which airports had the largest % of flights that \nwere more than 20 minutes late in 2017?") +
theme(legend.position = "none")
ggplotly(late_plot)
Create a table with 6 or fewer rows and 3 or fewer columns that summarizes which airport is the “worst” in terms of late arrivals.
late_flights_over20_simple <- tbl(con_air, "flights") %>%
filter(year == 2017) %>%
group_by(origin) %>%
summarize(tot_depart = n(),
prop_late_over20 = mean(arr_delay > 20)) %>%
inner_join(tbl(con_air, "airports"),
by = c("origin" = "faa")) %>%
ungroup() %>%
group_by(name) %>%
select(name, tot_depart, prop_late_over20, -origin) %>%
arrange(desc(prop_late_over20)) %>%
head(6)
late_flights_over20_simple_df <- collect(late_flights_over20_simple)
late_flights_over20_simple_df
- An original SQL query and plot
Find the proportion of flights that arrive on time during winter months, with average flight distance, and sdee which airlines perform best by this metric.
on_time_winter <- tbl(con_air, "flights") %>%
filter(year == 2017, month %in% c(11, 12, 1, 2, 3)) %>%
group_by(carrier) %>%
summarize(tot_depart = n(),
avg_dist = mean(distance),
prop_on_time = mean(arr_delay <= 0)) %>%
select(tot_depart, avg_dist, carrier, prop_on_time)
on_time_winter_df <- collect(on_time_winter)
on_time_winter_df
on_time_winter %>%
show_query()
## <SQL>
## SELECT `tot_depart`, `avg_dist`, `carrier`, `prop_on_time`
## FROM (SELECT `carrier`, COUNT(*) AS `tot_depart`, AVG(`distance`) AS `avg_dist`, AVG(`arr_delay` <= 0.0) AS `prop_on_time`
## FROM `flights`
## WHERE ((`year` = 2017.0) AND (`month` IN (11.0, 12.0, 1.0, 2.0, 3.0)))
## GROUP BY `carrier`) `q01`
on_time_winter_df %>%
ggplot(aes(x = prop_on_time, y = fct_reorder(carrier, prop_on_time), fill = avg_dist)) +
geom_col() +
labs(title = "Airlines with the Highest Proportion of \nOn-Time Flights during Winter Months",
x = "Proportion of On-Time Arrivals",
y = "Carrier",
fill = "Average Flight Distance")

Function Friday
geom_sf() Tasks
states <- st_as_sf(maps::map("state",
plot = FALSE,
fill = TRUE))
counties <- st_as_sf(maps::map("county",
plot = FALSE,
fill = TRUE))
states <- states %>%
mutate(area = as.numeric(st_area(states)))
head(states)
- Change the color scheme of the map from the default blue (one option could be viridis).
ggplot(data = states) +
geom_sf(aes(fill = area)) +
scale_fill_viridis_c(option = "C") +
coord_sf(xlim = c(-127, -63),
ylim = c(24, 51),
expand = FALSE) +
theme_minimal()

- Add a dot (or any symbol you want) to the centroid of each state.
ggplot(data = states) +
geom_sf(aes(fill = area)) +
scale_fill_viridis_c(option = "C") +
stat_sf_coordinates(color = "white") +
coord_sf(xlim = c(-127, -63),
ylim = c(24, 51),
expand = FALSE) +
theme_minimal()

- Add a layer onto the map with the counties.
ggplot() +
geom_sf(data = states, aes(fill = area)) +
geom_sf(data = counties, fill = NA, color = "black") +
scale_fill_viridis_c(option = "C") +
coord_sf(xlim = c(-127, -63),
ylim = c(24, 51),
expand = FALSE) +
theme_minimal()

- Change the coordinates of the map to zoom in on your favorite state.
ggplot() +
geom_sf(data = states %>% filter(ID == "new york"), aes(fill = area)) +
geom_sf(data = counties, fill = NA, color = "black") +
scale_fill_viridis_c(option = "C") +
coord_sf(xlim = c(-80, -71.8),
ylim = c(40.4, 45.1),
expand = FALSE) +
theme_minimal()

tidytext tasks
These are tweets from Twitter handles that are connected to the Internet Research Agency (IRA), a Russian “troll factory.” The majority of these tweets were posted from 2015-2017, but the datasets encompass tweets from February 2012 to May 2018.
Three of the main categories of troll tweet that we will be focusing on are Left Trolls, Right Trolls, and News Feed. Left Trolls usually pretend to be BLM activists, aiming to divide the democratic party (in this context, being pro-Bernie so that votes are taken away from Hillary). Right trolls imitate Trump supporters, and News Feed handles are “local news aggregators,” typically linking to legitimate news.
For our upcoming analyses, some important variables are:
author (handle sending the tweet)
content (text of the tweet)
language (language of the tweet)
publish_date (date and time the tweet was sent)
- Read in Troll Tweets Dataset
troll_tweets <- read_csv("https://raw.githubusercontent.com/fivethirtyeight/russian-troll-tweets/master/IRAhandle_tweets_12.csv")
- Basic Data Cleaning and Exploration
- Remove rows where the tweet was in a language other than English
- Report the dimensions of the dataset
- Create two or three basic exploratory plots of the data (ex. plot of the different locations from which tweets were posted, plot of the account category of a tweet)
troll_tweets_clean <- troll_tweets %>%
filter(language == "English")
dim(troll_tweets_clean)
## [1] 175966 21
troll_tweets_clean %>%
filter(account_type == "Left") %>%
ggplot() +
geom_point(aes(x = following, y = followers, color = updates), alpha = .5)

troll_tweets_clean %>%
filter(account_type == "Right") %>%
ggplot() +
geom_point(aes(x = following, y = followers, color = updates), alpha = .5)

troll_tweets_clean %>%
filter(account_type == "Russian") %>%
ggplot() +
geom_point(aes(x = following, y = followers, color = updates), alpha = .5)

- Unnest Tokens: We want each row to represent a word from a tweet, rather than an entire tweet.
troll_tweets_untoken <- troll_tweets_clean %>%
unnest_tokens(word, content)
troll_tweets_untoken
- Remove stopwords
troll_tweets_cleaner <- troll_tweets_untoken %>%
anti_join(stop_words)
troll_tweets_cleaner <- troll_tweets_cleaner %>%
filter(!word %in% c("http", "https", "t.co", "rt", "amp", 0:9, "a:z"))
- See how often top words appear
troll_tweets_small <- troll_tweets_cleaner %>%
count(word) %>%
slice_max(order_by = n, n = 50) # 50 most occurring words
# visualize the number of times the 50 top words appear
ggplot(troll_tweets_small,
aes(y = fct_reorder(word,n), x = n)) +
geom_col()

- Sentiment Analysis
- Get the sentiments using the “bing” parameter (which classifies words into “positive” or “negative”)
- Report how many positive and negative words there are in the dataset. Are there more positive or negative words, and why do you think this might be?
get_sentiments("bing")
troll_tweets_sentiment <- troll_tweets_cleaner %>%
inner_join(sentiments)
troll_tweets_sentiment %>%
count(sentiment)
I believe that there are many more negative words because people tend to focus more on negative events, and tweets by bots or trolls tend to only focus on negative events in an attempt to stir up negativity.
- Using the troll_tweets_small dataset, make a wordcloud:
- That is sized by the number of times that a word appears in the tweets
- That is colored by sentiment (positive or negative)
troll_tweets_small %>%
with(wordcloud(word, n, max.words = 50))

troll_tweets_sentiment %>%
group_by(word) %>%
mutate(n = n()) %>%
acast(word ~ sentiment, value.var = "n", fill = 0) %>%
comparison.cloud(colors = c("red","green"),
max.words = 50)

LS0tCnRpdGxlOiAiQXNzaWdubWVudCAjMyIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpCmBgYAoKYGBge3IgbGlicmFyaWVzfQojIFNFRSBtb2RlbGRhdGEgcGFja2FnZSBmb3IgbmV3IGRhdGFzZXRzCmxpYnJhcnkodGlkeXZlcnNlKSAgICAgICAgICMgZm9yIGdyYXBoaW5nIGFuZCBkYXRhIGNsZWFuaW5nCmxpYnJhcnkodGlkeW1vZGVscykgICAgICAgICMgZm9yIG1vZGVsaW5nCmxpYnJhcnkoc3RhY2tzKSAgICAgICAgICAgICMgZm9yIHN0YWNraW5nIG1vZGVscwpsaWJyYXJ5KG5hbmlhcikgICAgICAgICAgICAjIGZvciBleGFtaW5pbmcgbWlzc2luZyB2YWx1ZXMgKE5BcykKbGlicmFyeShsdWJyaWRhdGUpICAgICAgICAgIyBmb3IgZGF0ZSBtYW5pcHVsYXRpb24KbGlicmFyeShtb2Rlcm5kaXZlKSAgICAgICAgIyBmb3IgS2luZyBDb3VudHkgaG91c2luZyBkYXRhCmxpYnJhcnkoREFMRVgpICAgICAgICAgICAgICMgZm9yIG1vZGVsIGludGVycHJldGF0aW9uICAKbGlicmFyeShEQUxFWHRyYSkgICAgICAgICAgIyBmb3IgZXh0ZW5zaW9uIG9mIERBTEVYCmxpYnJhcnkocGF0Y2h3b3JrKSAgICAgICAgICMgZm9yIGNvbWJpbmluZyBwbG90cyBuaWNlbHkKbGlicmFyeShkYnBseXIpICAgICAgICAgICAgIyBmb3IgU1FMIHF1ZXJ5ICJjaGVhdGluZyIgLSBwYXJ0IG9mIHRpZHl2ZXJzZSBidXQgbmVlZHMgdG8gYmUgbG9hZGVkIHNlcGFyYXRlbHkKbGlicmFyeShtZHNyKSAgICAgICAgICAgICAgIyBmb3IgYWNjZXNzaW5nIHNvbWUgZGF0YWJhc2VzIC0gZ29lcyB3aXRoIE1vZGVybiBEYXRhIFNjaWVuY2Ugd2l0aCBSIHRleHRib29rCmxpYnJhcnkoUk15U1FMKSAgICAgICAgICAgICMgZm9yIGFjY2Vzc2luZyBNeVNRTCBkYXRhYmFzZXMKbGlicmFyeShSU1FMaXRlKSAgICAgICAgICAgIyBmb3IgYWNjZXNzaW5nIFNRTGl0ZSBkYXRhYmFzZXMKbGlicmFyeShwbG90bHkpICAgICAgICAgICAgIyBmb3IgaW50ZXJhY3RpdmUgcGxvdHMKCiNtYXBwaW5nCmxpYnJhcnkobWFwcykgICAgICAgICAgICAgICMgZm9yIGJ1aWx0LWluIG1hcHMKbGlicmFyeShzZikgICAgICAgICAgICAgICAgIyBmb3IgbWFraW5nIG1hcHMgdXNpbmcgZ2VvbV9zZgpsaWJyYXJ5KGdndGhlbWVzKSAgICAgICAgICAjIExpc2EgYWRkZWQgLSBJIGxpa2UgdGhlbWVfbWFwKCkgZm9yIG1hcHMgOikKCiN0aWR5dGV4dApsaWJyYXJ5KHRpZHl0ZXh0KSAgICAgICAgICAjIGZvciB0ZXh0IGFuYWx5c2lzLCB0aGUgdGlkeSB3YXkhCmxpYnJhcnkodGV4dGRhdGEpICAgICAgICAgIApsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KHdvcmRjbG91ZCkgICAgICAgICAjIGZvciB3b3JkY2xvdWQKbGlicmFyeShzdG9wd29yZHMpCmBgYAoKIyMgUHV0IGl0IG9uIEdpdEh1YiEKCltIZXJlXShodHRwczovL2dpdGh1Yi5jb20vYWxleGRlbnpsZXIvU1RBVDQ5NF9zaXRlX0RlbnpsZXIpIGlzIG15IEdpdEh1YiBsaW5rLgoKCiMjIExvY2FsIEludGVycHJldGFibGUgTWFjaGluZSBMZWFybmluZwoKV2Ugd2lsbCB1c2UgdGhlIEtpbmcgQ291bnR5IGhvdXNpbmcgZGF0YS4KCmBgYHtyfQpkYXRhKCJob3VzZV9wcmljZXMiKQoKIyBDcmVhdGUgbG9nX3ByaWNlIGFuZCBkcm9wIHByaWNlIHZhcmlhYmxlCmhvdXNlX3ByaWNlcyA8LSBob3VzZV9wcmljZXMgJT4lIAogIG11dGF0ZShsb2dfcHJpY2UgPSBsb2cocHJpY2UsIGJhc2UgPSAxMCkpICU+JSAKICAjIG1ha2UgYWxsIGludGVnZXJzIG51bWVyaWMgLi4uIGZpeGVzIHByZWRpY3Rpb24gcHJvYmxlbQogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuaW50ZWdlciksIGFzLm51bWVyaWMpKSAlPiUgCiAgc2VsZWN0KC1wcmljZSkKYGBgCgoKIyMjIFBhcnQgMQoKICBDaG9vc2UgMyBuZXcgb2JzZXJ2YXRpb25zIGFuZCBkbyB0aGUgZm9sbG93aW5nIGZvciBlYWNoIG9ic2VydmF0aW9uOgogICogQ29uc3RydWN0IGEgYnJlYWstZG93biBwbG90IHVzaW5nIHRoZSBkZWZhdWx0IG9yZGVyaW5nLiBJbnRlcnByZXQgdGhlIHJlc3VsdGluZyBncmFwaC4gV2hpY2ggdmFyaWFibGVzIGNvbnRyaWJ1dGUgbW9zdCB0byBlYWNoIG9ic2VydmF0aW9u4oCZcyBwcmVkaWN0aW9uPwogICogQ29uc3RydWN0IGEgU0hBUCBncmFwaCBhbmQgaW50ZXJwcmV0IGl0LiBEb2VzIGl0IHRlbGwgYSBzaW1pbGFyIHN0b3J5IHRvIHRoZSBicmVhay1kb3duIHBsb3Q/CiAgKiBDb25zdHJ1Y3QgYSBMSU1FIGdyYXBoIChmb2xsb3cgbXkgY29kZSBjYXJlZnVsbHkpLiBIb3cgY2xvc2UgaXMgZWFjaCBvcmlnaW5hbCBwcmVkaWN0aW9uIHRvIHRoZSBwcmVkaWN0aW9uIGZyb20gdGhlIGxvY2FsIG1vZGVsPyBJbnRlcnByZXQgdGhlIHJlc3VsdC4gWW91IGNhbiBhbHNvIHRyeSB1c2luZyBmZXdlciBvciBtb3JlIHZhcmlhYmxlcyBpbiB0aGUgbG9jYWwgbW9kZWwgdGhhbiBMaXNhIHVzZWQgaW4gdGhlIGV4YW1wbGUuCiAgCiAgCmBgYHtyfQpzZXQuc2VlZCg0OTQpCgpob3VzZV9zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGhvdXNlX3ByaWNlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvcCA9IC43NSkKaG91c2VfdHJhaW4gPC0gdHJhaW5pbmcoaG91c2Vfc3BsaXQpCmhvdXNlX3Rlc3QgPC0gdGVzdGluZyhob3VzZV9zcGxpdCkKCmhvdXNlX3Jhbmdlcl9yZWNpcGUgPC0gCiAgcmVjaXBlKGZvcm11bGEgPSBsb2dfcHJpY2UgfiAuLCAKICAgICAgICAgZGF0YSA9IGhvdXNlX3RyYWluKSAlPiUgCiAgc3RlcF9kYXRlKGRhdGUsIAogICAgICAgICAgICBmZWF0dXJlcyA9ICJtb250aCIpICU+JSAKICB1cGRhdGVfcm9sZShhbGxfb2YoYygiaWQiLAogICAgICAgICAgICAgICAgICAgICAgICJkYXRlIikpLAogICAgICAgICAgICAgIG5ld19yb2xlID0gImV2YWx1YXRpdmUiKQoKaG91c2VfcmFuZ2VyX3NwZWMgPC0gCiAgcmFuZF9mb3Jlc3QobXRyeSA9IDYsIAogICAgICAgICAgICAgIG1pbl9uID0gMTAsIAogICAgICAgICAgICAgIHRyZWVzID0gMjAwKSAlPiUgCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikKCmhvdXNlX3Jhbmdlcl93b3JrZmxvdyA8LSAKICB3b3JrZmxvdygpICU+JSAKICBhZGRfcmVjaXBlKGhvdXNlX3Jhbmdlcl9yZWNpcGUpICU+JSAKICBhZGRfbW9kZWwoaG91c2VfcmFuZ2VyX3NwZWMpIAoKc2V0LnNlZWQoNDk0KQpob3VzZV9yYW5nZXJfZml0IDwtIGhvdXNlX3Jhbmdlcl93b3JrZmxvdyAlPiUgCiAgZml0KGhvdXNlX3RyYWluKQpgYGAKICAKICAKYGBge3J9CmhvdXNlX3JmX2V4cGxhaW4gPC0gCiAgZXhwbGFpbl90aWR5bW9kZWxzKAogICAgbW9kZWwgPSBob3VzZV9yYW5nZXJfZml0LAogICAgZGF0YSA9IGhvdXNlX3RyYWluICU+JSBzZWxlY3QoLWxvZ19wcmljZSksIAogICAgeSA9IGhvdXNlX3RyYWluICU+JSAgcHVsbChsb2dfcHJpY2UpLAogICAgbGFiZWwgPSAiUmFuZG9tIEZvcmVzdCIKICApCmBgYAogIApgYGB7cn0Kc2V0LnNlZWQoNDk0KQpuZXdfb2JzMSA8LSBob3VzZV90ZXN0ICU+JSBzbGljZSg0MjM5KQpuZXdfb2JzMiA8LSBob3VzZV90ZXN0ICU+JSBzbGljZSgyOTQpCm5ld19vYnMzIDwtIGhvdXNlX3Rlc3QgJT4lIHNsaWNlKDMpCgoxMF4obmV3X29iczEkbG9nX3ByaWNlKQoxMF4obmV3X29iczIkbG9nX3ByaWNlKQoxMF4obmV3X29iczMkbG9nX3ByaWNlKQpgYGAKICAKIyMjIyBCcmVhay1Eb3duIFBsb3RzCiAgCmBgYHtyfQpzZXQuc2VlZCg0OTQpCgpob3VzZV9wcF9yZjEgPC0gcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSBob3VzZV9yZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMxLAogICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiYnJlYWtfZG93biIpCgpob3VzZV9wcF9yZjIgPC0gcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSBob3VzZV9yZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMyLAogICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiYnJlYWtfZG93biIpCgpob3VzZV9wcF9yZjMgPC0gcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSBob3VzZV9yZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMzLAogICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiYnJlYWtfZG93biIpCgpwbG90KGhvdXNlX3BwX3JmMSkKcGxvdChob3VzZV9wcF9yZjIpCnBsb3QoaG91c2VfcHBfcmYzKQpgYGAKCiAgKiBUaGUgYXZlcmFnZSBsb2cgcHJpY2UgcmVtYWlucyB0aGUgc2FtZSBhY3Jvc3MgYWxsIG9ic2VydmF0aW9ucywgYXMgaXQgaXMgdGhlIGF2ZXJhZ2UgbG9nIHByaWNlIHdoZW4gYXBwbGllZCB0byBhbGwgb2YgdGhlIHRyYWluaW5nIGRhdGEuIFRoaXMgbG9nIHByaWNlIGlzIDUuNjY1LCBvciBcJDQ2MiwzODEuIFdlICBzZWUgdGhhdCBob2xkaW5nIGxhdGl0dWRlIGNvbnN0YW50IGF0IDQ3LjY5OCBhZmZlY3RzIHRoZSBvdmVyYWxsIGF2ZXJhZ2UgcHJpY2UgdGhlIG1vc3QsIGFuZCBpbmNyZWFzZXMgdGhlIGxvZyBwcmljZSBieSAwLjA4NCwgb3IgXCQ5OCw2NjYuOTUuIFRoZSBwcmVkaWN0ZWQgbG9nIHByaWNlIGZvciB0aGlzIG9ic2VydmF0aW9uIGlzIDUuNjUzLCBvciBcJDQ0OSw3NzkuOTAuCgogICogVGhlIHNlY29uZCBwbG90IHNob3dzIHVzIHRoYXQgZml4aW5nIG91ciBsYXRpdHVkZSBhdCA0Ny41MTk3IGRlY3JlYXNlcyB0aGUgYXZlcmFnZSBsb2cgcHJpY2UgYnkgMC4wNTEsIG9yIFwkNTEsMjMxLjMwLiBXZSBhbHNvIHNlZSB0aGF0IGhvbGRpbmcgdGhpcyBsYXRpdHVkZSBjb25zdGFudCBhZmZlY3RzIHRoZSBwcmljZSB0aGUgbW9zdC4gRmluYWxseSwgdGhlIHByZWRpY3RlZCBsb2cgcHJpY2UgZm9yIHRoaXMgb2JzZXJ2YXRpb24gaXMgNS41OTksIG9yIFwkMzk3LDE5MS41MC4KCiAgKiBUaGUgdGhpcmQgcGxvdCBzaG93cyB1cyB0aGF0IGZpeGluZyBvdXIgbGF0aXR1ZGUgYXQgNDcuNjEyNyBpbmNyZWFzZXMgdGhlIGxvZyBwcmljZSBieSAwLjEwOSwgb3IgXCQxMzEsOTExLjEwLiBIb2xkaW5nIHRoaXMgbGF0aXR1ZGUgY29uc3RhbnQgYWZmZWN0cyB0aGUgcHJlZGljdGVkIHByaWNlIHRoZSBtb3N0LiBUaGlzIHByZWRpY3RlZCBsb2cgcHJpY2UgaXMgNS41OSwgb3IgXCQzODksMDQ1LjEwLgoKCiMjIyMgU2hhcGxleSBBZGRpdGl2ZSBFeHBsYW5hdGlvbnNzIChTSEFQKSBQbG90cwoKYGBge3IsIGNhY2hlPVRSVUV9CmhvdXNlX3JmX3NoYXAxIDwtcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSBob3VzZV9yZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMSwKICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzaGFwIiwKICAgICAgICAgICAgICAgICAgICAgICAgQiA9IDEwCikKCmhvdXNlX3JmX3NoYXAyIDwtcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSBob3VzZV9yZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMiwKICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzaGFwIiwKICAgICAgICAgICAgICAgICAgICAgICAgQiA9IDEwCikKCmhvdXNlX3JmX3NoYXAzIDwtcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSBob3VzZV9yZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMywKICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzaGFwIiwKICAgICAgICAgICAgICAgICAgICAgICAgQiA9IDEwCikKCnBsb3QoaG91c2VfcmZfc2hhcDEpCnBsb3QoaG91c2VfcmZfc2hhcDIpCnBsb3QoaG91c2VfcmZfc2hhcDMpCmBgYAoKCiAgKiBUaGUgZmlyc3QgU0hBUCBwbG90IHNob3dzIGEgc2ltaWxhciBlZmZlY3QgdG8gdGhlIGZpcnN0IGJyZWFrLWRvd24gcGxvdCwgd2hlcmUgZ3JhZGUgaXMgdGhlIGhpZ2hlc3QgY29udHJpYnV0aW5nIHZhcmlhYmxlLCBob3dldmVyIHRoaXMgdGltZSBpdCBpcyBpbiBhIG5lZ2F0aXZlIHdheS4gVGhlIHNlY29uZCBhbmQgdGhpcmQgU0hBUCBwbG90cyBib3RoIHNob3cgdGhhdCBsYXRpdHVkZSBpcyB0aGUgaGlnaGVzdCBjb250cmlidXRpbmcgdmFyaWFibGUsIHdoaWNoIGlzIGluIGFjY29yZGFuY2Ugd2l0aCB0aGUgYnJlYWstZG93biBwbG90cywgdGhpcyB0aW1lIGJvdGggYWZmZWN0aW5nIHRoZSBwcmVkaWN0ZWQgcHJpY2UgaW4gdGhlIHNhbWUgd2F5IGFzIHRoZSBicmVhay1kb3duIHBsb3RzLiAKICAKCiMjIyMgTG9jYWwgSW50ZXJwcmV0YWJsZSBNb2RlbC1BZ25vc3RpYyBFeHBsYW5hdGlvbiAoTElNRSkgUGxvdHMKCmBgYHtyfQpzZXQuc2VlZCg0OTQpCgptb2RlbF90eXBlLmRhbGV4X2V4cGxhaW5lciA8LSBEQUxFWHRyYTo6bW9kZWxfdHlwZS5kYWxleF9leHBsYWluZXIKcHJlZGljdF9tb2RlbC5kYWxleF9leHBsYWluZXIgPC0gREFMRVh0cmE6OnByZWRpY3RfbW9kZWwuZGFsZXhfZXhwbGFpbmVyCgpob3VzZV9saW1lX3JmMSA8LSBwcmVkaWN0X3N1cnJvZ2F0ZShleHBsYWluZXIgPSBob3VzZV9yZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KC1sb2dfcHJpY2UpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9mZWF0dXJlcyA9IDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fcGVybXV0YXRpb25zID0gMTAwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJsaW1lIikKCmhvdXNlX2xpbWVfcmYyIDwtIHByZWRpY3Rfc3Vycm9nYXRlKGV4cGxhaW5lciA9IGhvdXNlX3JmX2V4cGxhaW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMyICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLWxvZ19wcmljZSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2ZlYXR1cmVzID0gNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9wZXJtdXRhdGlvbnMgPSAxMDAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImxpbWUiKQoKaG91c2VfbGltZV9yZjMgPC0gcHJlZGljdF9zdXJyb2dhdGUoZXhwbGFpbmVyID0gaG91c2VfcmZfZXhwbGFpbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3X29ic2VydmF0aW9uID0gbmV3X29iczMgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCgtbG9nX3ByaWNlKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSA1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3Blcm11dGF0aW9ucyA9IDEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAibGltZSIpCgpob3VzZV9saW1lX3JmMSAlPiUgCiAgc2VsZWN0KG1vZGVsX3IyLCBtb2RlbF9wcmVkaWN0aW9uLCBwcmVkaWN0aW9uKSAlPiUgCiAgZGlzdGluY3QoKQoKaG91c2VfbGltZV9yZjIgJT4lIAogIHNlbGVjdChtb2RlbF9yMiwgbW9kZWxfcHJlZGljdGlvbiwgcHJlZGljdGlvbikgJT4lIAogIGRpc3RpbmN0KCkKCmhvdXNlX2xpbWVfcmYzICU+JSAKICBzZWxlY3QobW9kZWxfcjIsIG1vZGVsX3ByZWRpY3Rpb24sIHByZWRpY3Rpb24pICU+JSAKICBkaXN0aW5jdCgpCmBgYAoKYGBge3J9CnNldC5zZWVkKDQ5NCkKCnBsb3QoaG91c2VfbGltZV9yZjEpICsKICBsYWJzKHggPSAiVmFyaWFibGUiKQoKcGxvdChob3VzZV9saW1lX3JmMikgKwogIGxhYnMoeCA9ICJWYXJpYWJsZSIpCgpwbG90KGhvdXNlX2xpbWVfcmYzKSArCiAgbGFicyh4ID0gIlZhcmlhYmxlIikKYGBgCgoKICAqIFBsb3QgMSBzaG93cyB1cyB0aGF0IHRoZSBwcmVkaWN0ZWQgbG9nIHByaWNlIGlzIGFib3V0IDUuNTc3LCBhbmQgdGhhdCBncmFkZSBpcyBvbmNlIGFnYWluIHRoZSBtb3N0IGltcG9ydGFudCBpbiB0aGUgbG9jYWwgbW9kZWwuIFRoaXMgbW9kZWwgYWxzbyBwZXJmb3JtcyBxdWl0ZSBwb29ybHksIGFzIHRoZSBleHBsYW5hdGlvbiBmaXQgaXMgb25seSAwLjExLiBUaGlzIGlzIHRoZSBleGFjdCBwcmVkaWN0aW9uICh0byB0aHJlZSBkaWdpdHMpIHRoYXQgdGhlIG9yaWdpbmFsIG1vZGVsIGhhZC4KICAKICAqIFBsb3QgMiBzaG93cyB1cyB0aGF0IHRoZSBwcmVkaWN0ZWQgbG9nIHByaWNlIGlzIGFib3V0IDUuODYsIGFuZCB0aGF0IHRoZSBsaXZpbmcgcm9vbSBhcmVhIGFuZCBsYXRpdHVkZSBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IGluIHRoZSBsb2NhbCBtb2RlbCwgYWxiZWl0IGluIG9wcG9zaXRlIHdheXMuIFRoaXMgbW9kZWwgcGVyZm9ybXMgYmV0dGVyIHRoYW4gdGhlIGZpcnN0LCBhcyB0aGUgZXhwbGFuYXRpb24gZml0IGlzIDAuNDUuIFRoaXMgaXMgdGhlIGV4YWN0IHByZWRpY3Rpb24gKHRvIHR3byBkaWdpdHMpIGFzIHRoZSBvcmlnaW5hbCBtb2RlbC4KICAKICAqIFBsb3QgMyBzaG93cyB1cyB0aGF0IHRoZSBwcmVkaWN0ZWQgbG9nIHByaWNlIGlzIGFib3V0IDUuNDYsIGFuZCB0aGF0IHRoZSBncmFkZSBpcyB0aGUgbW9zdCBpbXBvcnRhbnQgdG8gdGhlIGxvY2FsIG1vZGVsLiBUaGlzIG1vZGVsIHBlcmZvcm1zIHRoZSB3b3JzdCBvdXQgb2YgdGhlIHRocmVlLCBhcyB0aGUgZXhwbGFuYXRpb24gZml0IGlzIG9ubHkgMC4xLiBUaGlzIGlzIHRoZSBleGFjdCBwcmVkaWN0aW9uICh0byB0d28gZGlnaXRzKSBhcyB0aGUgb3JpZ2luYWwgbW9kZWwuIAoKCiMjIyBQYXJ0IDIKICAKICBEZXNjcmliZSBob3cgeW91IHdvdWxkIHVzZSB0aGUgaW50ZXJwcmV0YWJsZSBtYWNoaW5lIGxlYXJuaW5nIHRvb2xzIHdl4oCZdmUgbGVhcm5lZCAoYm90aCBsb2NhbCBhbmQgZ2xvYmFsKSBpbiBmdXR1cmUgbWFjaGluZSBsZWFybmluZyBwcm9qZWN0cz8gSG93IGRvZXMgZWFjaCBvZiB0aGVtIGhlbHAgeW91PwogIAogICogVGhlc2UgbWV0aG9kcyBwcm92aWRlIGEgaGVscGZ1bCBhbHRlcm5hdGl2ZSB0byBhIHNpbXBsZSB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3QsIGFzIHdlIGFyZSBhYmxlIHRvIGlkZW50aWZ5IHZhcmlhYmxlIGltcG9ydGFuY2Ugb24gdGhlIGxldmVsIG9mIGluZGl2aWR1YWwgb2JzZXJ2YXRpb25zLiBXZSBhcmUgYWJsZSB0byBzZWUgd2h5IGVhY2ggb2JzZXJ2YXRpb24gY29udGFpbnMgdGhlIHJlc3VsdCB0aGF0IGl0IGRvZXMuIFRoZSBicmVhay1kb3duIHBsb3QgbWV0aG9kIGFsbG93cyB1cyB0byBzZWUgaG93IHRoZSBlbnRpcmUgdHJhaW5pbmcgZGF0YXNldCBpcyBhZmZlY3RlZCB3aGVuIHdlIGFwcGx5IGEgY2VydGFpbiBtb2RlbCB0byBpdC4gVGhlIFNIQVAgcGxvdHMgYWxsb3cgdXMgdG8gc2VlIGhvdyBlYWNoIHZhcmlhYmxlIGNvbnRyaWJ1dGVzIGlmIHdlIGNoYW5nZSB0aGUgb3JkZXIgb2YgdGhlIHZhcmlhYmxlcyAoaW4gdGhpcyBhc3NpZ25tZW50LCB0aGUgY29udHJpYnV0aW9uIGNoYW5nZXMgYmVjYXVzZSB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbCBpcyBub3QgYWRkaXRpdmUpLiBUaHJvdWdoIHRoaXMsIHdlIGFyZSBhYmxlIHRvIHNlZSBob3cgdHJ1ZSB0aGUgZWZmZWN0IGlzIHRocm91Z2ggdGhlIGJveHBsb3RzIG9uIHRvcCBvZiB0aGUgU0hBUCBwbG90LiBGaW5hbGx5LCB0aGUgTElNRSBwbG90cyBhbGxvdyB1cyB0byBzZWUgdmFyaWFibGUgaW1wb3J0YW5jZSBpbiBjb25qdW5jdGlvbiB3aXRoIG1vZGVsIHBlcmZvcm1hbmNlLCB3aGljaCBnaXZlcyB1cyBtb3JlIGluc2lnaHQgaW50byBob3cgdHJ1c3R3b3J0aHkgb3VyIHJlc3VsdCBpcy4KICAKCiMjIFNRTCAKCkkgd2lsbCB1c2UgdGhlIGBhaXJsaW5lc2AgZGF0YSBmcm9tIHRoZSBTUUwgZGF0YWJhc2UgdGhhdCBMaXNhIHVzZWQgaW4gdGhlIGV4YW1wbGUgaW4gdGhlIHR1dG9yaWFsLgoKKipUYXNrcyoqCgogIDEuIENyZWF0ZSBhIFNRTCBjaHVuayBhbmQgYW4gZXF1aXZhbGVudCBSIGNvZGUgY2h1bmsgdGhhdCBkb2VzIHRoZSBmb2xsb3dpbmc6IGZvciAyMDE3LCBmb3IgZWFjaCBhaXJwb3J0ICh3aXRoIGl0cyBuYW1lLCBub3QgY29kZSksIGFuZCBtb250aCBmaW5kIHRoZSB0b3RhbCBudW1iZXIgb2YgZGVwYXJ0aW5nIGZsaWdodHMsIHRoZSBhdmVyYWdlIGRpc3RhbmNlIG9mIHRoZSBmbGlnaHQsIGFuZCB0aGUgcHJvcG9ydGlvbiBvZiBmbGlnaHRzIHRoYXQgYXJyaXZlZCBtb3JlIHRoYW4gMjAgbWludXRlcyBsYXRlLiBJbiB0aGUgUiBjb2RlIGNodW5rLCB3cml0ZSB0aGlzIG91dCB0byBhIGRhdGFzZXQuCiAgCmBgYHtyfQpjb25fYWlyIDwtIGRiQ29ubmVjdF9zY2lkYigiYWlybGluZXMiKQpgYGAKCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uX2Fpcn0KREVTQ1JJQkUgZmxpZ2h0cwpgYGAKCmBgYHtzcWwgY29ubmVjdGlvbj1jb25fYWlyfQpERVNDUklCRSBhaXJwb3J0cwpgYGAKCgpgYGB7cn0KbGF0ZV9mbGlnaHRzX292ZXIyMCA8LSB0YmwoY29uX2FpciwgImZsaWdodHMiKSAlPiUgCiAgZmlsdGVyKHllYXIgPT0gMjAxNykgJT4lCiAgZ3JvdXBfYnkob3JpZ2luLCBtb250aCkgJT4lIAogIHN1bW1hcml6ZSh0b3RfZGVwYXJ0ID0gbigpLAogICAgICAgICAgICBhdmdfZGlzdCA9IG1lYW4oZGlzdGFuY2UpLAogICAgICAgICAgICBwcm9wX2xhdGVfb3ZlcjIwID0gbWVhbihhcnJfZGVsYXkgPiAyMCkpICU+JSAKICBpbm5lcl9qb2luKHRibChjb25fYWlyLCAiYWlycG9ydHMiKSwKICAgICAgICAgICAgIGJ5ID0gYygib3JpZ2luIiA9ICJmYWEiKSkgJT4lIAogIHNlbGVjdChuYW1lLCBtb250aCwgdG90X2RlcGFydCwgYXZnX2Rpc3QsIHByb3BfbGF0ZV9vdmVyMjApCgpsYXRlX2ZsaWdodHNfb3ZlcjIwX2RmIDwtIGNvbGxlY3QobGF0ZV9mbGlnaHRzX292ZXIyMCkKbGF0ZV9mbGlnaHRzX292ZXIyMF9kZgpgYGAKCgpgYGB7cn0KbGF0ZV9mbGlnaHRzX292ZXIyMCAlPiUgCiAgc2hvd19xdWVyeSgpCmBgYAogIAogICogV2l0aCB0aGUgZGF0YXNldCB5b3Ugd3JvdGUgb3V0LCBjcmVhdGUgYSBncmFwaCB0aGF0IGhlbHBzIGlsbHVzdHJhdGUgdGhlIOKAnHdvcnN04oCdIGFpcnBvcnRzIGluIHRlcm1zIG9mIGxhdGUgYXJyaXZhbHMuCiAgCmBgYHtyfQpsYXRlX3Bsb3QgPC0gZ2dwbG90KGxhdGVfZmxpZ2h0c19vdmVyMjBfZGYgJT4lIAogICAgICAgICAgICAgICAgICAgIG11dGF0ZShtb250aCA9IGFzLmZhY3Rvcihtb250aCksCiAgICAgICAgICAgICAgICAgICAgbWVhbl9wcm9wID0gbWVhbihwcm9wX2xhdGVfb3ZlcjIwKSkgJT4lIAogICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KG1lYW5fcHJvcCwgbmFtZSkgJT4lIAogICAgICAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyhtZWFuX3Byb3ApKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgaGVhZCgyMDApLCAKICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IHRvdF9kZXBhcnQsIHkgPSBtZWFuX3Byb3ApKSArCmdlb21fcG9pbnQoYWVzKHNpemUgPSBhdmdfZGlzdCwgY29sb3IgPSBuYW1lKSwgYWxwaGEgPSAuNSkgKwpsYWJzKHggPSAiQXZlcmFnZSBEaXN0YW5jZSBUcmF2ZWxlZCIsCiAgICAgeSA9ICJUb3RhbCBEZXBhcnR1cmVzIiwKICAgICB0aXRsZSA9ICJXaGljaCBhaXJwb3J0cyBoYWQgdGhlIGxhcmdlc3QgJSBvZiBmbGlnaHRzIHRoYXQgXG53ZXJlIG1vcmUgdGhhbiAyMCBtaW51dGVzIGxhdGUgaW4gMjAxNz8iKSArCnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmdncGxvdGx5KGxhdGVfcGxvdCkKYGBgCgoKQ3JlYXRlIGEgdGFibGUgd2l0aCA2IG9yIGZld2VyIHJvd3MgYW5kIDMgb3IgZmV3ZXIgY29sdW1ucyB0aGF0IHN1bW1hcml6ZXMgd2hpY2ggYWlycG9ydCBpcyB0aGUg4oCcd29yc3TigJ0gaW4gdGVybXMgb2YgbGF0ZSBhcnJpdmFscy4KCmBgYHtyfQpsYXRlX2ZsaWdodHNfb3ZlcjIwX3NpbXBsZSA8LSB0YmwoY29uX2FpciwgImZsaWdodHMiKSAlPiUgCiAgZmlsdGVyKHllYXIgPT0gMjAxNykgJT4lCiAgZ3JvdXBfYnkob3JpZ2luKSAlPiUgCiAgc3VtbWFyaXplKHRvdF9kZXBhcnQgPSBuKCksCiAgICAgICAgICAgIHByb3BfbGF0ZV9vdmVyMjAgPSBtZWFuKGFycl9kZWxheSA+IDIwKSkgJT4lIAogIGlubmVyX2pvaW4odGJsKGNvbl9haXIsICJhaXJwb3J0cyIpLAogICAgICAgICAgICAgYnkgPSBjKCJvcmlnaW4iID0gImZhYSIpKSAlPiUKICB1bmdyb3VwKCkgJT4lIAogIGdyb3VwX2J5KG5hbWUpICU+JSAKICBzZWxlY3QobmFtZSwgdG90X2RlcGFydCwgcHJvcF9sYXRlX292ZXIyMCwgLW9yaWdpbikgJT4lIAogIGFycmFuZ2UoZGVzYyhwcm9wX2xhdGVfb3ZlcjIwKSkgJT4lIAogIGhlYWQoNikKICAKbGF0ZV9mbGlnaHRzX292ZXIyMF9zaW1wbGVfZGYgPC0gY29sbGVjdChsYXRlX2ZsaWdodHNfb3ZlcjIwX3NpbXBsZSkKbGF0ZV9mbGlnaHRzX292ZXIyMF9zaW1wbGVfZGYKYGBgCgogIDIuIEFuIG9yaWdpbmFsIFNRTCBxdWVyeSBhbmQgcGxvdAogIAogIEZpbmQgdGhlIHByb3BvcnRpb24gb2YgZmxpZ2h0cyB0aGF0IGFycml2ZSBvbiB0aW1lIGR1cmluZyB3aW50ZXIgbW9udGhzLCB3aXRoIGF2ZXJhZ2UgZmxpZ2h0IGRpc3RhbmNlLCBhbmQgc2RlZSB3aGljaCBhaXJsaW5lcyBwZXJmb3JtIGJlc3QgYnkgdGhpcyBtZXRyaWMuCiAgCmBgYHtyfQpvbl90aW1lX3dpbnRlciA8LSB0YmwoY29uX2FpciwgImZsaWdodHMiKSAlPiUgCiAgZmlsdGVyKHllYXIgPT0gMjAxNywgbW9udGggJWluJSBjKDExLCAxMiwgMSwgMiwgMykpICU+JQogIGdyb3VwX2J5KGNhcnJpZXIpICU+JSAKICBzdW1tYXJpemUodG90X2RlcGFydCA9IG4oKSwKICAgICAgICAgICAgYXZnX2Rpc3QgPSBtZWFuKGRpc3RhbmNlKSwKICAgICAgICAgICAgcHJvcF9vbl90aW1lID0gbWVhbihhcnJfZGVsYXkgPD0gMCkpICU+JSAKICBzZWxlY3QodG90X2RlcGFydCwgYXZnX2Rpc3QsIGNhcnJpZXIsIHByb3Bfb25fdGltZSkKCm9uX3RpbWVfd2ludGVyX2RmIDwtIGNvbGxlY3Qob25fdGltZV93aW50ZXIpCm9uX3RpbWVfd2ludGVyX2RmCmBgYAogIApgYGB7cn0Kb25fdGltZV93aW50ZXIgJT4lIAogIHNob3dfcXVlcnkoKQpgYGAKICAKYGBge3J9Cm9uX3RpbWVfd2ludGVyX2RmICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBwcm9wX29uX3RpbWUsIHkgPSBmY3RfcmVvcmRlcihjYXJyaWVyLCBwcm9wX29uX3RpbWUpLCBmaWxsID0gYXZnX2Rpc3QpKSArCiAgZ2VvbV9jb2woKSArCiAgbGFicyh0aXRsZSA9ICJBaXJsaW5lcyB3aXRoIHRoZSBIaWdoZXN0IFByb3BvcnRpb24gb2YgXG5Pbi1UaW1lIEZsaWdodHMgZHVyaW5nIFdpbnRlciBNb250aHMiLAogICAgICAgeCA9ICJQcm9wb3J0aW9uIG9mIE9uLVRpbWUgQXJyaXZhbHMiLAogICAgICAgeSA9ICJDYXJyaWVyIiwKICAgICAgIGZpbGwgPSAiQXZlcmFnZSBGbGlnaHQgRGlzdGFuY2UiKQpgYGAKICAKCiMjIEZ1bmN0aW9uIEZyaWRheQoKIyMjIGBnZW9tX3NmKClgIFRhc2tzCgpgYGB7cn0Kc3RhdGVzIDwtIHN0X2FzX3NmKG1hcHM6Om1hcCgic3RhdGUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBUUlVFKSkKCmNvdW50aWVzIDwtIHN0X2FzX3NmKG1hcHM6Om1hcCgiY291bnR5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFRSVUUpKQpgYGAKCmBgYHtyfQpzdGF0ZXMgPC0gc3RhdGVzICU+JQogIG11dGF0ZShhcmVhID0gYXMubnVtZXJpYyhzdF9hcmVhKHN0YXRlcykpKQpoZWFkKHN0YXRlcykKYGBgCgoxLiBDaGFuZ2UgdGhlIGNvbG9yIHNjaGVtZSBvZiB0aGUgbWFwIGZyb20gdGhlIGRlZmF1bHQgYmx1ZSAob25lIG9wdGlvbiBjb3VsZCBiZSB2aXJpZGlzKS4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHN0YXRlcykgKwogIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJDIikgKwogIGNvb3JkX3NmKHhsaW0gPSBjKC0xMjcsIC02MyksIAogICAgICAgICAgIHlsaW0gPSBjKDI0LCA1MSksIAogICAgICAgICAgIGV4cGFuZCA9IEZBTFNFKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKMi4gQWRkIGEgZG90IChvciBhbnkgc3ltYm9sIHlvdSB3YW50KSB0byB0aGUgY2VudHJvaWQgb2YgZWFjaCBzdGF0ZS4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHN0YXRlcykgKwogIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJDIikgKwogIHN0YXRfc2ZfY29vcmRpbmF0ZXMoY29sb3IgPSAid2hpdGUiKSArCiAgY29vcmRfc2YoeGxpbSA9IGMoLTEyNywgLTYzKSwgCiAgICAgICAgICAgeWxpbSA9IGMoMjQsIDUxKSwgCiAgICAgICAgICAgZXhwYW5kID0gRkFMU0UpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKMy4gQWRkIGEgbGF5ZXIgb250byB0aGUgbWFwIHdpdGggdGhlIGNvdW50aWVzLgoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBzdGF0ZXMsIGFlcyhmaWxsID0gYXJlYSkpICsKICBnZW9tX3NmKGRhdGEgPSBjb3VudGllcywgZmlsbCA9IE5BLCBjb2xvciA9ICJibGFjayIpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiQyIpICsKICBjb29yZF9zZih4bGltID0gYygtMTI3LCAtNjMpLCAKICAgICAgICAgICB5bGltID0gYygyNCwgNTEpLCAKICAgICAgICAgICBleHBhbmQgPSBGQUxTRSkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCgo0LiBDaGFuZ2UgdGhlIGNvb3JkaW5hdGVzIG9mIHRoZSBtYXAgdG8gem9vbSBpbiBvbiB5b3VyIGZhdm9yaXRlIHN0YXRlLgoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBzdGF0ZXMgJT4lIGZpbHRlcihJRCA9PSAibmV3IHlvcmsiKSwgYWVzKGZpbGwgPSBhcmVhKSkgKwogIGdlb21fc2YoZGF0YSA9IGNvdW50aWVzLCBmaWxsID0gTkEsIGNvbG9yID0gImJsYWNrIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJDIikgKwogIGNvb3JkX3NmKHhsaW0gPSBjKC04MCwgLTcxLjgpLCAKICAgICAgICAgICB5bGltID0gYyg0MC40LCA0NS4xKSwKICAgICAgICAgICBleHBhbmQgPSBGQUxTRSkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCgoKCiMjIyBgdGlkeXRleHRgIHRhc2tzCgpUaGVzZSBhcmUgdHdlZXRzIGZyb20gVHdpdHRlciBoYW5kbGVzIHRoYXQgYXJlIGNvbm5lY3RlZCB0byB0aGUgSW50ZXJuZXQgUmVzZWFyY2ggQWdlbmN5IChJUkEpLCBhIFJ1c3NpYW4g4oCcdHJvbGwgZmFjdG9yeS7igJ0gVGhlIG1ham9yaXR5IG9mIHRoZXNlIHR3ZWV0cyB3ZXJlIHBvc3RlZCBmcm9tIDIwMTUtMjAxNywgYnV0IHRoZSBkYXRhc2V0cyBlbmNvbXBhc3MgdHdlZXRzIGZyb20gRmVicnVhcnkgMjAxMiB0byBNYXkgMjAxOC4KClRocmVlIG9mIHRoZSBtYWluIGNhdGVnb3JpZXMgb2YgdHJvbGwgdHdlZXQgdGhhdCB3ZSB3aWxsIGJlIGZvY3VzaW5nIG9uIGFyZSBMZWZ0IFRyb2xscywgUmlnaHQgVHJvbGxzLCBhbmQgTmV3cyBGZWVkLiAqKkxlZnQgVHJvbGxzKiogdXN1YWxseSBwcmV0ZW5kIHRvIGJlIEJMTSBhY3RpdmlzdHMsIGFpbWluZyB0byBkaXZpZGUgdGhlIGRlbW9jcmF0aWMgcGFydHkgKGluIHRoaXMgY29udGV4dCwgYmVpbmcgcHJvLUJlcm5pZSBzbyB0aGF0IHZvdGVzIGFyZSB0YWtlbiBhd2F5IGZyb20gSGlsbGFyeSkuICoqUmlnaHQgdHJvbGxzKiogaW1pdGF0ZSBUcnVtcCBzdXBwb3J0ZXJzLCBhbmQgKipOZXdzIEZlZWQqKiBoYW5kbGVzIGFyZSDigJxsb2NhbCBuZXdzIGFnZ3JlZ2F0b3JzLOKAnSB0eXBpY2FsbHkgbGlua2luZyB0byBsZWdpdGltYXRlIG5ld3MuCgpGb3Igb3VyIHVwY29taW5nIGFuYWx5c2VzLCBzb21lIGltcG9ydGFudCB2YXJpYWJsZXMgYXJlOgoKKiAqKmF1dGhvcioqIChoYW5kbGUgc2VuZGluZyB0aGUgdHdlZXQpCiogKipjb250ZW50KiogKHRleHQgb2YgdGhlIHR3ZWV0KQoqICoqbGFuZ3VhZ2UqKiAobGFuZ3VhZ2Ugb2YgdGhlIHR3ZWV0KQoqICoqcHVibGlzaF9kYXRlKiogKGRhdGUgYW5kIHRpbWUgdGhlIHR3ZWV0IHdhcyBzZW50KQoKCiAgMS4gUmVhZCBpbiBUcm9sbCBUd2VldHMgRGF0YXNldAogIApgYGB7ciwgY2FjaGU9VFJVRX0KdHJvbGxfdHdlZXRzIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZml2ZXRoaXJ0eWVpZ2h0L3J1c3NpYW4tdHJvbGwtdHdlZXRzL21hc3Rlci9JUkFoYW5kbGVfdHdlZXRzXzEyLmNzdiIpCmBgYAogIAogIDIuIEJhc2ljIERhdGEgQ2xlYW5pbmcgYW5kIEV4cGxvcmF0aW9uCiAgCiAgYS4gUmVtb3ZlIHJvd3Mgd2hlcmUgdGhlIHR3ZWV0IHdhcyBpbiBhIGxhbmd1YWdlIG90aGVyIHRoYW4gRW5nbGlzaAogIGIuIFJlcG9ydCB0aGUgZGltZW5zaW9ucyBvZiB0aGUgZGF0YXNldAogIGMuIENyZWF0ZSB0d28gb3IgdGhyZWUgYmFzaWMgZXhwbG9yYXRvcnkgcGxvdHMgb2YgdGhlIGRhdGEgKGV4LiBwbG90IG9mIHRoZSBkaWZmZXJlbnQgbG9jYXRpb25zIGZyb20gd2hpY2ggdHdlZXRzIHdlcmUgcG9zdGVkLCBwbG90IG9mIHRoZSBhY2NvdW50IGNhdGVnb3J5IG9mIGEgdHdlZXQpCiAgCmBgYHtyfQp0cm9sbF90d2VldHNfY2xlYW4gPC0gdHJvbGxfdHdlZXRzICU+JSAKICBmaWx0ZXIobGFuZ3VhZ2UgPT0gIkVuZ2xpc2giKQoKZGltKHRyb2xsX3R3ZWV0c19jbGVhbikKYGBgCiAgCmBgYHtyfQp0cm9sbF90d2VldHNfY2xlYW4gJT4lIAogIGZpbHRlcihhY2NvdW50X3R5cGUgPT0gIkxlZnQiKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBmb2xsb3dpbmcsIHkgPSBmb2xsb3dlcnMsIGNvbG9yID0gdXBkYXRlcyksIGFscGhhID0gLjUpCmBgYAogIAogIApgYGB7cn0KdHJvbGxfdHdlZXRzX2NsZWFuICU+JSAKICBmaWx0ZXIoYWNjb3VudF90eXBlID09ICJSaWdodCIpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGZvbGxvd2luZywgeSA9IGZvbGxvd2VycywgY29sb3IgPSB1cGRhdGVzKSwgYWxwaGEgPSAuNSkKYGBgCiAgCiAgCmBgYHtyfQp0cm9sbF90d2VldHNfY2xlYW4gJT4lIAogIGZpbHRlcihhY2NvdW50X3R5cGUgPT0gIlJ1c3NpYW4iKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBmb2xsb3dpbmcsIHkgPSBmb2xsb3dlcnMsIGNvbG9yID0gdXBkYXRlcyksIGFscGhhID0gLjUpCmBgYAogIAogIAogIDMuIFVubmVzdCBUb2tlbnM6IFdlIHdhbnQgZWFjaCByb3cgdG8gcmVwcmVzZW50IGEgd29yZCBmcm9tIGEgdHdlZXQsIHJhdGhlciB0aGFuIGFuIGVudGlyZSB0d2VldC4KICAKYGBge3J9CnRyb2xsX3R3ZWV0c191bnRva2VuIDwtIHRyb2xsX3R3ZWV0c19jbGVhbiAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIGNvbnRlbnQpCgp0cm9sbF90d2VldHNfdW50b2tlbgpgYGAKICAKICA0LiBSZW1vdmUgc3RvcHdvcmRzCiAgCmBgYHtyfQp0cm9sbF90d2VldHNfY2xlYW5lciA8LSB0cm9sbF90d2VldHNfdW50b2tlbiAlPiUKICBhbnRpX2pvaW4oc3RvcF93b3JkcykKYGBgCiAgCmBgYHtyfQp0cm9sbF90d2VldHNfY2xlYW5lciA8LSB0cm9sbF90d2VldHNfY2xlYW5lciAlPiUKICBmaWx0ZXIoIXdvcmQgJWluJSBjKCJodHRwIiwgImh0dHBzIiwgInQuY28iLCAicnQiLCAiYW1wIiwgMDo5LCAiYTp6IikpCmBgYAogIAogIAogIDUuIFNlZSBob3cgb2Z0ZW4gdG9wIHdvcmRzIGFwcGVhcgogIApgYGB7cn0KdHJvbGxfdHdlZXRzX3NtYWxsIDwtIHRyb2xsX3R3ZWV0c19jbGVhbmVyICU+JQogIGNvdW50KHdvcmQpICU+JSAKICBzbGljZV9tYXgob3JkZXJfYnkgPSBuLCBuID0gNTApICMgNTAgbW9zdCBvY2N1cnJpbmcgd29yZHMKCiMgdmlzdWFsaXplIHRoZSBudW1iZXIgb2YgdGltZXMgdGhlIDUwIHRvcCB3b3JkcyBhcHBlYXIKZ2dwbG90KHRyb2xsX3R3ZWV0c19zbWFsbCwgCiAgICAgICBhZXMoeSA9IGZjdF9yZW9yZGVyKHdvcmQsbiksIHggPSBuKSkgKwogIGdlb21fY29sKCkKYGBgCiAgCiAgCiAgNi4gU2VudGltZW50IEFuYWx5c2lzCiAgCiAgYS4gR2V0IHRoZSBzZW50aW1lbnRzIHVzaW5nIHRoZSDigJxiaW5n4oCdIHBhcmFtZXRlciAod2hpY2ggY2xhc3NpZmllcyB3b3JkcyBpbnRvIOKAnHBvc2l0aXZl4oCdIG9yIOKAnG5lZ2F0aXZl4oCdKQogIGIuIFJlcG9ydCBob3cgbWFueSBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgd29yZHMgdGhlcmUgYXJlIGluIHRoZSBkYXRhc2V0LiBBcmUgdGhlcmUgbW9yZSBwb3NpdGl2ZSBvciBuZWdhdGl2ZSB3b3JkcywgYW5kIHdoeSBkbyB5b3UgdGhpbmsgdGhpcyBtaWdodCBiZT8KICAKYGBge3J9CmdldF9zZW50aW1lbnRzKCJiaW5nIikKCnRyb2xsX3R3ZWV0c19zZW50aW1lbnQgPC0gdHJvbGxfdHdlZXRzX2NsZWFuZXIgJT4lCiAgaW5uZXJfam9pbihzZW50aW1lbnRzKQoKdHJvbGxfdHdlZXRzX3NlbnRpbWVudCAlPiUgCiAgY291bnQoc2VudGltZW50KQpgYGAKICAKICBJIGJlbGlldmUgdGhhdCB0aGVyZSBhcmUgbWFueSBtb3JlIG5lZ2F0aXZlIHdvcmRzIGJlY2F1c2UgcGVvcGxlIHRlbmQgdG8gZm9jdXMgbW9yZSBvbiBuZWdhdGl2ZSBldmVudHMsIGFuZCB0d2VldHMgYnkgYm90cyBvciB0cm9sbHMgdGVuZCB0byBvbmx5IGZvY3VzIG9uIG5lZ2F0aXZlIGV2ZW50cyBpbiBhbiBhdHRlbXB0IHRvIHN0aXIgdXAgbmVnYXRpdml0eS4gCiAgCgogIDcuIFVzaW5nIHRoZSB0cm9sbF90d2VldHNfc21hbGwgZGF0YXNldCwgbWFrZSBhIHdvcmRjbG91ZDoKICBhLiBUaGF0IGlzIHNpemVkIGJ5IHRoZSBudW1iZXIgb2YgdGltZXMgdGhhdCBhIHdvcmQgYXBwZWFycyBpbiB0aGUgdHdlZXRzCiAgYi4gVGhhdCBpcyBjb2xvcmVkIGJ5IHNlbnRpbWVudCAocG9zaXRpdmUgb3IgbmVnYXRpdmUpCiAgCmBgYHtyfQp0cm9sbF90d2VldHNfc21hbGwgJT4lCiAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gNTApKQoKCnRyb2xsX3R3ZWV0c19zZW50aW1lbnQgJT4lCiAgZ3JvdXBfYnkod29yZCkgJT4lIAogIG11dGF0ZShuID0gbigpKSAlPiUgCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lCiAgY29tcGFyaXNvbi5jbG91ZChjb2xvcnMgPSBjKCJyZWQiLCJncmVlbiIpLAogICAgICAgICAgICAgICAgICAgbWF4LndvcmRzID0gNTApCmBgYAogIAoKIyMgIlVuZG9pbmciIEJpYXMKCiogVGhlIGJpYXMgc3RhcnRpbmcgaW4gdGhlIGRhdGEgcG9pbnQgaXMgYSB2ZXJ5IGNvbmNlcm5pbmcgcG9pbnQgYmVjYXVzZSBkYXRhIGNvbGxlY3Rpb24gaXMgc28gb2Z0ZW4gdW5mYWlyLiBIb3dldmVyLCBpdCBzZWVtcyBsaWtlIGEgbG90IG9mIHRoaXMgaXMgZHVlIHRvIGhvdyBkYXRhIGlzIHJlZ3VsYXRlZC4gRnJvbSB3aGF0IEkgdW5kZXJzdGFuZCwgYWxsIHJhY2UgYW5kIGdlbmRlciBkYXRhIGlzIHJlbGlhbnQgb24gc2VsZi1yZXBvcnQgbWVhc3VyZXMgdGhhdCBwZW9wbGUgYXJlIGFibGUgdG8gb3B0IG91dCBvZi4gSSBhbHNvIHVuZGVyc3RhbmQgdGhhdCBpbiBzb21lIHNpdHVhdGlvbnMgaXQgaXMgYWN0dWFsbHkgaWxsZWdhbCB0byBjb2xsZWN0IHJhY2Ugb3IgZ2VuZGVyIGRhdGEuIEJlY2F1c2Ugb2YgdGhpcyBpbmZvcm1hdGlvbiwgSSBhbSB3b3JyaWVkIGFib3V0IHRoZSBmdXR1cmUgb2YgZ2VuZGVyIGFuZCByYWNpYWwgYmlhcy4gSSBoYXZlIGFsd2F5cyBoYWQgd29ycmllcyBhYm91dCBzZWxmLXJlcG9ydCBtZXRob2RzLCBhcyB0aGV5IGFyZSBvZnRlbiB1bnJlbGlhYmxlIGZvciBhIG11bHRpdHVkZSBvZiByZWFzb25zLiBJbiBjZXJ0YWluIHNpdHVhdGlvbnMsIGluIG9yZGVyIHRvIGNyZWF0ZSBmYWlyIGFsZ29yaXRobXMsIGxhd3Mgb24gZ2VuZGVyIGFuZCByYWNlIHJlcG9ydGluZyB3aWxsIGhhdmUgdG8gY2hhbmdlIGZvciB0aGVzZSBhbGdvcml0aG1zIHRvIG1ha2UgYSB0cnVlIHBvc2l0aXZlIGRpZmZlcmVuY2UuIAoKCgoKCgoK